﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace Babelfish.Css
{
    public class CssParser
    {
        public List<Entity> RootList = new List<Entity>();
        private string _buffer = string.Empty;
        private List<Entity> _parseList = new List<Entity>();
        private char _lastChar = ' ';

        /// <summary>
        /// Parses the specified CSS string.
        /// </summary>
        /// <param name="cssString">The CSS string.</param>
        /// <returns></returns>
        public static List<Entity> Parse(string cssString)
        {
            var parser = new CssParser();
            return parser.ParseString(cssString);
        }

        /// <summary>
        /// Parses the string.
        /// </summary>
        /// <param name="css">The CSS string.</param>
        /// <returns></returns>
        public List<Entity> ParseString(string css)
        {
            var reader = new StringReader(css);

            while(true)
            {
                var c = reader.Read();
                
                if (c == -1)
                    break;

                ParseNextCharacter((char)c);
            }

            return RootList;
        }

        /// <summary>
        /// Gets a value indicating whether [current tag is type comment].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [current tag is type comment]; otherwise, <c>false</c>.
        /// </value>
        private bool CurrentTagIsTypeComment
        {
            get
            {
                return _parseList.LastOrDefault() != null && _parseList.LastOrDefault().Type == EntityType.Comment;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [current tag is type import].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [current tag is type import]; otherwise, <c>false</c>.
        /// </value>
        private bool CurrentTagIsTypeImport
        {
            get
            {
                return _parseList.LastOrDefault() != null && _parseList.LastOrDefault().Type == EntityType.Import;
            }
        }

        /// <summary>
        /// Parses the next character.
        /// </summary>
        /// <param name="character">The character.</param>
        private void ParseNextCharacter(char character)
        {
            if (ParseComments(character))
                return;

            if (ParseImports(character))
                return;
            
            //skip further parsing if we are inside a commented section of the css
            if (CurrentTagIsTypeComment || CurrentTagIsTypeImport)
            {
                _buffer += character;
                _lastChar = character;
                return;
            }

            switch (character)
            {
                case '{' :
                    StartTag(EntityType.Class, _buffer);
                    break;
                case '}' :
                    EndTag(EntityType.Class);
                    break;
                case ':':
                    StartTag(EntityType.Property, _buffer);
                    break;
                case ';':
                    EndTag(EntityType.Property);
                    break;
                default:
                    _buffer += character;
                    break;
            }

            _lastChar = character;
        }

        /// <summary>
        /// Parses the imports.
        /// </summary>
        /// <param name="character">The character.</param>
        /// <returns></returns>
        private bool ParseImports(char character)
        {
            if (_parseList.Count > 0 && !CurrentTagIsTypeImport)
                return false;

            if (character == '@')
            {
                StartTag(EntityType.Import, "@import");
                return true;
            }

            if (character == ';')
            {
                EndTag(EntityType.Import);
                return true;
            }

            return false;
        }
        /// <summary>
        /// Parses the comments.
        /// </summary>
        /// <returns></returns>
        private bool ParseComments(char character)
        {

            var tmp = Regex.Replace(_buffer + character, "\\s+", string.Empty);

            //multiline comment START
            if (!CurrentTagIsTypeComment && ((character == '*' && _lastChar == '/') || tmp.StartsWith("/*")))
            {
                _buffer = _buffer.Remove(_buffer.Length - 1);
                StartTag(EntityType.Comment, "/*");
                return true;
            }

            //single line comment START
            if (!CurrentTagIsTypeComment && ((character == '/' && _lastChar == '/') || tmp.StartsWith("//")))
            {
                _buffer = _buffer.Remove(_buffer.Length - 1);
                StartTag(EntityType.Comment, "//");
                return true;
            }

            //multiline comment STOP
            if (CurrentTagIsTypeComment && ((character == '/' && _lastChar == '*') || tmp.EndsWith("*/")) && _parseList.LastOrDefault().Name == "/*")
            {
                _buffer = _buffer.Remove(_buffer.Length - 1);
                EndTag(EntityType.Comment);
                return true;
            }

            //single line comment STOP
            if (CurrentTagIsTypeComment && character == '\n' && _parseList.LastOrDefault().Name == "//")
            {
                EndTag(EntityType.Comment);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Ends the tag.
        /// </summary>
        private void EndTag(EntityType type)
        {
            //no tag has been started
            if (_parseList.Count == 0)
                return;

            var entity = _parseList[_parseList.Count - 1];

            //if the tag is a property, comment or import we wanna know whats inside
            if (entity.Type != EntityType.Class)
                entity.Value = _buffer;

            //parse unterminated properties, like class{property:somevalue;property:somevalue}, terminate property and class tag
            if(entity.Type != type)
                _parseList.Remove(entity);

            if (_parseList.Count == 0)
                return;

            entity = _parseList[_parseList.Count - 1];
                        
            if (entity.Parent == null)
                RootList.Add(entity);

            _parseList.Remove(entity);

            _buffer = string.Empty;
        }

        /// <summary>
        /// Starts the tag.
        /// </summary>
        /// <param name="type">The type.</param>
        private void StartTag(EntityType type, string name)
        {
            var parent = _parseList.LastOrDefault();

            var entity = new Entity { Name = name.Trim(), Type = type, Children = new List<Entity>()};

            if (parent != null)
            {
                entity.Parent = parent;
                _parseList.Add(entity);
                parent.Children.Add(_parseList.LastOrDefault());
            }
            else
            {
                _parseList.Add(entity);
            }

            _buffer = string.Empty;
        }
        



    }
}
